package edu.hdu.hangzhe.bytecode;

import edu.hdu.hangzhe.bytecode.domain.ABI;
import edu.hdu.hangzhe.bytecode.domain.LocalVariable;
import edu.hdu.hangzhe.bytecode.domain.Method;
import edu.hdu.hangzhe.utils.ABILUtils;
import edu.hdu.hangzhe.utils.ASMUtils;
import org.objectweb.asm.*;

import java.util.ArrayList;
import java.util.HashMap;

import static org.objectweb.asm.Opcodes.*;
import static org.objectweb.asm.Opcodes.MULTIANEWARRAY;

public class MyMethodVisitor extends MethodVisitor {
    private Method method;
    private int lastLine;
    private Label lastLabel;
    // Label的第一个ABI
    private HashMap<String, ABI> labelABIMap;
    // label与对应编号
    private HashMap<String, Integer> labelLineMap;
    private int a = 0;

    public MyMethodVisitor(int api, Method method) {
        super(api);
        this.method = method;
        labelABIMap = new HashMap<>();
        labelLineMap = new HashMap<>();
    }

    private void preAddIns(ABI inst) {
        labelABIMap.putIfAbsent(lastLabel.toString(), inst);
    }

    @Override
    public void visitParameter(String name, int access) {
        System.out.println("param:" + name);
        super.visitParameter(name, access);
    }

    @Override
    public void visitInsn(int opcode) {
        System.out.println("visitInsn:" + ASMUtils.num2Ins(opcode));
        ABI inst = new ABI(ASMUtils.num2Ins(opcode), null);
        // TODO
//        if (opcode == ICONST_0 || opcode == ICONST_1 || opcode == ICONST_2 || opcode == ICONST_3 || opcode == ICONST_4 || opcode == ICONST_5) {
//            inst = new ABI(ASMUtils.num2Ins(opcode), null);
//        }
        preAddIns(inst);
        method.addIns(inst, lastLabel, lastLine);
        super.visitInsn(opcode);
    }

    @Override
    public void visitIntInsn(int opcode, int operand) {
        System.out.println("visitIntInsn:" + opcode);
        ABI inst = new ABI(ASMUtils.num2Ins(opcode), "NUM");
        preAddIns(inst);
        method.addIns(inst, lastLabel, lastLine);
        super.visitIntInsn(opcode, operand);
    }

    @Override
    public void visitVarInsn(int opcode, int var) {
        System.out.println("visitVarInsn:" + ASMUtils.num2Ins(opcode) + " " + var);
        ABI inst = new ABI(ASMUtils.num2Ins(opcode), Integer.toString(var));
        preAddIns(inst);
        method.addIns(inst, lastLabel, lastLine);
        super.visitVarInsn(opcode, var);
    }

    @Override
    public void visitTypeInsn(int opcode, String type) {
        System.out.println("visitTypeInsn:" + ASMUtils.num2Ins(opcode) + " " + type);
        ABI inst = new ABI(ASMUtils.num2Ins(opcode), type);
        preAddIns(inst);
        method.addIns(inst, lastLabel, lastLine);
        super.visitTypeInsn(opcode, type);
    }

    @Override
    public void visitFieldInsn(int opcode, String owner, String name, String descriptor) {
        System.out.println("visitFieldInsn:" + ASMUtils.num2Ins(opcode) + owner + "." + name + descriptor);
        ABI inst = new ABI(ASMUtils.num2Ins(opcode), owner + "." + name);
        preAddIns(inst);
        method.addIns(inst, lastLabel, lastLine);
        super.visitFieldInsn(opcode, owner, name, descriptor);
    }

    @Override
    public void visitMethodInsn(int opcode, String owner, String name, String descriptor, boolean isInterface) {
        System.out.println("visitMethodInsn:" + ASMUtils.num2Ins(opcode) + owner + "." + name + descriptor);
        ABI inst = new ABI(ASMUtils.num2Ins(opcode),  owner + "." + name + descriptor);
        preAddIns(inst);
        method.addIns(inst, lastLabel, lastLine);
        super.visitMethodInsn(opcode, owner, name, descriptor, isInterface);
    }

    @Override
    public void visitInvokeDynamicInsn(String name, String descriptor, Handle bootstrapMethodHandle, Object... bootstrapMethodArguments) {
        System.out.println("visitInvokeDynamicInsn:" + ASMUtils.num2Ins(INVOKEDYNAMIC) + name + "(" + descriptor + ")");
        ABI inst = new ABI(ASMUtils.num2Ins(INVOKEDYNAMIC),  name + "(" + descriptor + ")");
        preAddIns(inst);
        method.addIns(inst, lastLabel, lastLine);
        super.visitInvokeDynamicInsn(name, descriptor, bootstrapMethodHandle, bootstrapMethodArguments);
    }

    @Override
    public void visitJumpInsn(int opcode, Label label) {
        System.out.println("visitJumpInsn:" + ASMUtils.num2Ins(opcode) + label);
        ABI inst = new ABI(ASMUtils.num2Ins(opcode),  label.toString());
        preAddIns(inst);
        method.addIns(inst, lastLabel, lastLine);
        super.visitJumpInsn(opcode, label);
    }

    @Override
    public void visitLabel(Label label) {
        System.out.println("visitLabel:" + label.toString());
        lastLabel = label;
        labelLineMap.put(label.toString(), a++);
        super.visitLabel(label);
    }

    @Override
    public void visitLdcInsn(Object value) {
        System.out.println("visitLdcInsn:LDC " + value);
        String type = value.getClass().getName();
        String tmp = "";
        if (type.equals("java.lang.String")) {
            tmp = "S";
        } else if (type.equals("java.lang.Integer")) {
            tmp = "I";
        } else {
            tmp = "F";
        }
        ABI inst = new ABI(ASMUtils.num2Ins(LDC),  String.valueOf(value) + "[" + tmp + "]");
        preAddIns(inst);
        method.addIns(inst, lastLabel, lastLine);
        super.visitLdcInsn(value);
    }

    @Override
    public void visitIincInsn(int var, int increment) {
        System.out.println("visitIincInsn:" + var + " " + increment);
        ABI inst = new ABI(ASMUtils.num2Ins(IINC), null);
        preAddIns(inst);
        method.addIns(inst, lastLabel, lastLine);
        super.visitIincInsn(var, increment);
    }

    @Override
    public void visitTableSwitchInsn(int min, int max, Label dflt, Label... labels) {
        System.out.println("visitTableSwitchInsn:");

        ABI inst = new ABI(ASMUtils.num2Ins(TABLESWITCH), null);
        preAddIns(inst);
        method.addIns(inst, lastLabel, lastLine);
        super.visitTableSwitchInsn(min, max, dflt, labels);
    }

    @Override
    public void visitLookupSwitchInsn(Label dflt, int[] keys, Label[] labels) {
        System.out.println("visitLookupSwitchInsn:");
        ABI inst = new ABI(ASMUtils.num2Ins(LOOKUPSWITCH), null);
        preAddIns(inst);
        method.addIns(inst, lastLabel, lastLine);
        super.visitLookupSwitchInsn(dflt, keys, labels);
    }

    @Override
    public void visitMultiANewArrayInsn(String descriptor, int numDimensions) {
        System.out.println("visitMultiANewArrayInsn:" + descriptor + "|" + numDimensions);
        ABI inst = new ABI(ASMUtils.num2Ins(MULTIANEWARRAY), descriptor + "|" + numDimensions);
        preAddIns(inst);
        method.addIns(inst, lastLabel, lastLine);
        super.visitMultiANewArrayInsn(descriptor, numDimensions);
    }

    @Override
    public void visitLocalVariable(String name, String descriptor, String signature, Label start, Label end, int index) {
        System.out.println("visitLocalVariable:" + name + ":" + descriptor + ":" + start.toString() + ":" + end.toString() + " " + index);
        method.addLocalVariable(name, index, start, end);
        super.visitLocalVariable(name, descriptor, signature, start, end, index);
    }

    @Override
    public void visitLineNumber(int line, Label start) {
        System.out.println("visitLineNumber:" + line + ":" + start.toString());
        lastLine = line;
        super.visitLineNumber(line, start);
    }

    @Override
    public void visitEnd() {
        parseLocalVariable();
        parseVarInsn();
        parseJumpInsn(method.ABIL);
        // parseSwitchInsn();
        super.visitEnd();
    }
    private void parseLocalVariable()  {
        method.setLabelLineMap(labelLineMap);
    }
    private void parseVarInsn() {
        for (ABI a : method.ABIL) {
            if (ABILUtils.isVarIns(a)) {
                LocalVariable var = method.getBestVariable(Integer.parseInt(a.value), labelLineMap.get(a.getLabel().toString()));
                if (var == null) {
                    a.value = "s0";
                } else {
                    a.value = var.getName();
                }
            }
        }
    }
    private void parseJumpInsn(ArrayList<ABI> ABIL) {
        ArrayList<ABI> tmpList = new ArrayList<>();
        for (ABI a : ABIL) {
            if (ABILUtils.isJumpIns(a)) {
                ABI t = labelABIMap.get(a.value);
                if (ABILUtils.isJumpIns(t) && labelABIMap.containsKey(t.value)) {
                    tmpList.add(a);
                    continue;
                }
                a.value = labelABIMap.get(a.value).toString();
            }
        }
        if (!tmpList.isEmpty()) {
            parseJumpInsn(tmpList);
        }
    }

}